1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 
12 module hip.systems.game;
13 import hip.audio;
14 import hip.view;
15 import hip.error.handler;
16 import hip.api.view.scene;
17 
18 import hip.systems.timer_manager;
19 import hip.event.dispatcher;
20 import hip.event.handlers.keyboard;
21 import hip.event.handlers.mouse;
22 import hip.windowing.events;
23 import hip.hiprenderer.renderer;
24 import hip.graphics.g2d.renderer2d;
25 public import hip.event.handlers.input_listener;
26 
27 version(WebAssembly) version = CustomRuntime;
28 version(CustomRuntimeTest) version = CustomRuntime;
29 version(PSVita) version = CustomRuntime;
30 
31 version(Standalone)
32 {
33     pragma(mangle, "HipremeEngineMainScene")
34     extern(C) AScene HipremeEngineMainScene();
35 }
36 
37 version(Load_DScript)
38 {
39     import hip.systems.hotload;
40     import hip.systems.compilewatcher;
41     private bool getDubError(string line)
42     {
43         import hip.console.log;
44         import hip.util.string:indexOf, lastIndexOf;
45         int errInd = line.indexOf("Warning:");
46         if(errInd == -1) errInd = line.indexOf("Error:"); //Check first for warnings
47         return errInd != -1;
48     }
49     extern(System) AScene function() HipremeEngineGameInit;
50     extern(System) void function() HipremeEngineGameDestroy;
51 }
52 
53 
54 class GameSystem
55 {
56     /**
57      * Holds the member that generates the events as inputs
58      */
59     EventDispatcher dispatcher;
60     AScene[] scenes;
61 
62     HipInputListener inputListener;
63     HipInputListener scriptInputListener;
64     string projectDir, buildCommand = "redub build";
65     ///Resets delta time after a reload for not jumping frames.
66     protected bool shouldResetDelta;
67     protected __gshared AScene externalScene;
68 
69     version(Load_DScript)
70     {
71         static CompileWatcher watcher;
72         protected static HotloadableDLL hotload;
73     }
74     bool hasFinished;
75     bool isInUpdate;
76     float fps = 0;
77     float targetFPS = 0;
78     float fpsAccumulator = 0;
79     size_t frames = 0;
80 
81     this(float targetFPS)
82     {
83         this.targetFPS = targetFPS;
84         dispatcher = new EventDispatcher(HipRenderer.window, &this.isInUpdate);
85         dispatcher.addOnResizeListener((uint width, uint height)
86         {
87             HipRenderer.width  = width;
88             HipRenderer.height = height;
89             resizeRenderer2D(width, height);
90             foreach (AScene s; scenes)
91                 s.onResize(width, height);
92         });
93         inputListener = new HipInputListener(dispatcher);
94         scriptInputListener = new HipInputListener(dispatcher);
95 
96         import hip.console.log;
97         inputListener.addKeyboardListener(HipKey.ESCAPE,
98             (meta){hasFinished = true;}
99         );
100         // inputListener.addKeyboardListener(HipKey.F1,
101         //     (meta){import hip.bind.interpreters; reloadInterpreter();},
102         //     HipButtonType.up
103         // );
104 
105         version(Load_DScript)
106         {
107             inputListener.addKeyboardListener(HipKey.F5,(meta)
108                 {
109                     import hip.console.log;
110                     rawlog("Recompiling and Reloading game ");
111                     recompileReloadExternalScene();
112                 }, HipButtonType.up
113             );
114         }
115         import hip.api.input.core;
116         setHipInput(dispatcher);
117         setHipInputListener(scriptInputListener);
118 
119     }
120 
121     void loadGame(string gameDll, string buildCommand)
122     {
123         version(Load_DScript)
124         {
125             import hip.filesystem.hipfs;
126             import hip.util.path;
127             import hip.util.system;
128             import hip.util.string:indexOf;
129             import hip.console.log;
130 
131 
132             if(gameDll.isAbsolutePath && HipFS.absoluteIsFile(gameDll))
133             {
134                 projectDir = gameDll.dirName;
135             }
136             else if(!gameDll.extension && gameDll.indexOf("projects/") == -1)
137             {
138                 projectDir = joinPath("projects", gameDll);
139                 gameDll = joinPath("projects", gameDll, gameDll);
140             }
141             else
142                 projectDir = gameDll;
143 
144             watcher = new CompileWatcher(projectDir, null, ["d"]).run;
145             this.buildCommand = buildCommand ? buildCommand : this.buildCommand;
146 
147             hotload = new HotloadableDLL(gameDll, (void* lib)
148             {
149                 ErrorHandler.assertLazyExit(lib != null, "No library " ~ gameDll ~ " was found");
150                 HipremeEngineGameInit =
151                     cast(typeof(HipremeEngineGameInit))
152                     dynamicLibrarySymbolLink(lib, "HipremeEngineGameInit");
153                 ErrorHandler.assertLazyExit(HipremeEngineGameInit != null,
154                 "HipremeEngineGameInit wasn't found when looking into "~gameDll);
155                 HipremeEngineGameDestroy =
156                     cast(typeof(HipremeEngineGameDestroy))
157                     dynamicLibrarySymbolLink(lib, "HipremeEngineGameDestroy");
158                 ErrorHandler.assertLazyExit(HipremeEngineGameDestroy != null,
159                 "HipremeEngineGameDestroy wasn't found when looking into "~gameDll);
160             });
161         }
162     }
163 
164     void recompileGame()
165     {
166         version(Load_DScript)
167         {
168             scope string[] errors;
169             import hip.console.log;
170 
171             static void logFun(string line)
172             {
173                 rawlog(line);
174             }
175 
176             int status = hip.systems.compilewatcher.recompileGame(projectDir, buildCommand, &getDubError, &logFun,  errors);
177             //2 == up to date
178             if(errors.length)
179             {
180                 loglnError(errors);
181                 foreach(err; errors) loglnError(err);
182             }
183             else
184                 hotload.reload();
185         }
186     }
187 
188     void startGame()
189     {
190         import hip.view.load_scene;
191         import hip.assetmanager;
192         version(Test)
193         {
194             // addScene(new SoundTestScene());
195             // addScene(new ChainTestScene());
196             // addScene(new AssetTest());
197             import hip.view.testscene;
198             import hip.console.log;
199             import hip.api.data.commons;
200             mixin LoadReferencedAssets!(["hip.view.testscene"]);
201             loadReferenced;
202             hiplog("starting test scene.");
203             addScene(new TestScene());
204         }
205         else version(Load_DScript)
206         {
207             ErrorHandler.assertExit(HipremeEngineGameInit != null, "No game was loaded");
208             externalScene = HipremeEngineGameInit();
209             addScene(externalScene);
210         }
211         else version(Standalone)
212         {
213             import hip.console.log;
214             hiplog("Starting Game");
215             externalScene = HipremeEngineMainScene();
216             addScene(externalScene);
217         }
218 
219         LoadingScene load = new LoadingScene();
220         addScene(load, false);
221         HipAssetManager.addOnLoadingFinish(()
222         {
223             removeScene(load);
224         });
225     }
226 
227     void recompileReloadExternalScene()
228     {
229         version(Load_DScript)
230         {
231             import hip.util.array:remove;
232             import hip.console.log;
233             if(hotload)
234             {
235                 shouldResetDelta = true;
236                 rawlog("Recompiling game");
237                 HipTimerManager.clearSchedule();
238                 scriptInputListener.clearAll();
239                 HipremeEngineGameDestroy();
240                 scenes.remove(externalScene);
241                 externalScene = null;
242                 recompileGame(); // Calls hotload.reload();
243                 startGame();
244             }
245         }
246     }
247 
248     /**
249     *   Adding a scene will initialize them, while checking for assets referencing for auto loading them.
250     */
251     void addScene(AScene s, bool isOnLoadFinish = true)
252     {
253         import hip.assetmanager;
254 
255         auto onLoadFn = ()
256         {
257             import hip.console.log;
258             version(CustomRuntime)
259             {
260                 s.preload();
261                 loglnWarn("Initializing scene ", s.getName);
262                 s.initialize();
263                 scenes~= s;
264             }
265             else
266             {
267                 try{
268                     s.preload();
269                     loglnWarn("Initializing scene ", s.getName);
270                     s.initialize();
271                     scenes~= s;
272                 }
273                 catch (Error e){scriptFatalError(e);}
274             }
275         };
276         if(isOnLoadFinish)
277             HipAssetManager.addOnLoadingFinish(onLoadFn);
278         else
279             onLoadFn();
280         // }
281     }
282 
283     void removeScene(AScene s)
284     {
285         import hip.util.array:remove;
286         remove(scenes, s);
287     }
288 
289 
290     bool update(float deltaTime)
291     {
292         import hip.assetmanager;
293         isInUpdate = true;
294         frames++;
295         fpsAccumulator+= deltaTime;
296         if(shouldResetDelta)
297         {
298             deltaTime = 0;
299             shouldResetDelta = false;
300         }
301         if(fpsAccumulator >= 1.0)
302         {
303             import hip.console.log;
304             // logln("FPS: ", frames);
305             frames = 0;
306             fpsAccumulator = 0;
307         }
308         import hip.console.log;
309 
310         HipAudio.update();
311         HipTimerManager.update(deltaTime);
312         HipAssetManager.update();
313 
314         version(Load_DScript)
315         {
316             if(watcher.update())
317                 recompileReloadExternalScene();
318         }
319         dispatcher.handleEvent();
320         dispatcher.pollGamepads(deltaTime);
321         inputListener.update();
322         scriptInputListener.update();
323 
324         if(hasFinished || dispatcher.hasQuit)
325             return false;
326         foreach(s; scenes)
327         {
328             import hip.console.log;
329             version(CustomRuntime)
330             {
331                 if(s is null) logln("SCENE IS NULL");
332                 else s.update(deltaTime);
333             }
334             else
335             {
336                 try
337                 {
338                     if(s is null) logln("SCENE IS NULL");
339                     else s.update(deltaTime);
340                 }
341                 catch (Error e){scriptFatalError(e);}
342             }
343         }
344 
345         return true;
346     }
347 
348     version(CustomRuntime){}
349     else
350     void scriptFatalError(Throwable e, string file = __FILE__, size_t line = __LINE__, string func = __PRETTY_FUNCTION__)
351     {
352         import hip.console.log;
353         import hip.util.path;
354         loglnError(e.msg, ". Project: (", projectDir, ") at file (", e.file, ":",e.line, ")");
355         quit();
356         ErrorHandler.assertExit(false, "Script Fatal Error", file, line, __MODULE__, func);
357     }
358     void render()
359     {
360         version(CustomRuntime)
361         {
362             foreach (AScene s; scenes)
363                 s.render();
364         }
365         else
366         {
367             try
368             {
369                 foreach (AScene s; scenes)
370                     s.render();
371             }
372             catch(Throwable e){scriptFatalError(e);}
373         }
374         HipTimerManager.render();
375     }
376     void postUpdate()
377     {
378         dispatcher.postUpdate();
379         isInUpdate = false;
380     }
381 
382     void quit()
383     {
384         version(Load_DScript)
385         {
386             if(hotload !is null)
387             {
388                 if(HipremeEngineGameDestroy != null)
389                     HipremeEngineGameDestroy();
390                 scenes.length = 0;
391                 externalScene = null;
392                 hotload.dispose();
393                 watcher.stop();
394             }
395         }
396         import hip.assetmanager;
397         HipAssetManager.dispose();
398 
399     }
400 }